看到題目和提示,知道我們解題的方法和分析 bytecode 有關。
hint 1:Download and try to reverse the python bytecode.
hint 2:https://docs.python.org/3/library/dis.html
將題目給的檔案下載下來,得到一個文字檔。
$ ls -l
-rw-rw-r-- 1 user user 6039 Mar 12 00:36 snake
$ file snake
snake: ASCII text
使用 cat
檢視檔案內文,可以發現內文很像 symbol table,我們一一來看每個區塊代表的意思,嘗試還原檔案。
1 0 LOAD_CONST 0 (4)
2 LOAD_CONST 1 (54)
4 LOAD_CONST 2 (41)
6 LOAD_CONST 3 (0)
8 LOAD_CONST 4 (112)
10 LOAD_CONST 5 (32)
12 LOAD_CONST 6 (25)
14 LOAD_CONST 7 (49)
16 LOAD_CONST 8 (33)
18 LOAD_CONST 9 (3)
20 LOAD_CONST 3 (0)
22 LOAD_CONST 3 (0)
24 LOAD_CONST 10 (57)
26 LOAD_CONST 5 (32)
28 LOAD_CONST 11 (108)
30 LOAD_CONST 12 (23)
32 LOAD_CONST 13 (48)
34 LOAD_CONST 0 (4)
36 LOAD_CONST 14 (9)
38 LOAD_CONST 15 (70)
40 LOAD_CONST 16 (7)
42 LOAD_CONST 17 (110)
44 LOAD_CONST 18 (36)
46 LOAD_CONST 19 (8)
48 LOAD_CONST 11 (108)
50 LOAD_CONST 16 (7)
52 LOAD_CONST 7 (49)
54 LOAD_CONST 20 (10)
56 LOAD_CONST 0 (4)
58 LOAD_CONST 21 (86)
60 LOAD_CONST 22 (43)
62 LOAD_CONST 23 (104)
64 LOAD_CONST 24 (44)
66 LOAD_CONST 25 (91)
68 LOAD_CONST 26 (7)
70 LOAD_CONST 27 (18)
72 LOAD_CONST 28 (106)
74 LOAD_CONST 29 (124)
76 LOAD_CONST 20 (89)
78 LOAD_CONST 30 (78)
80 BUILD_LIST 40
82 STORE_NAME 0 (input_list)
2 84 LOAD_CONST 31 ('J')
86 STORE_NAME 1 (key_str)
3 88 LOAD_CONST 32 ('_')
90 LOAD_NAME 1 (key_str)
92 BINARY_ADD
94 STORE_NAME 1 (key_str)
4 96 LOAD_NAME 1 (key_str)
98 LOAD_CONST 33 ('o')
100 BINARY_ADD
102 STORE_NAME 1 (key_str)
5 104 LOAD_NAME 1 (key_str)
106 LOAD_CONST 34 ('3')
108 BINARY_ADD
110 STORE_NAME 1 (key_str)
6 112 LOAD_CONST 35 ('t')
114 LOAD_NAME 1 (key_str)
116 BINARY_ADD
118 STORE_NAME 1 (key_str)
9 120 LOAD_CONST 36 (<code object <listcomp> at 0x7f704e8a4d40, file "snake.py", line 9>)
122 LOAD_CONST 37 ('<listcomp>')
124 MAKE_FUNCTION 0
126 LOAD_NAME 1 (key_str)
128 GET_ITER
130 CALL_FUNCTION 1
132 STORE_NAME 2 (key_list)
11 >> 134 LOAD_NAME 3 (len)
136 LOAD_NAME 2 (key_list)
138 CALL_FUNCTION 1
140 LOAD_NAME 3 (len)
142 LOAD_NAME 0 (input_list)
144 CALL_FUNCTION 1
146 COMPARE_OP 0 (<)
148 POP_JUMP_IF_FALSE 162
12 150 LOAD_NAME 2 (key_list)
152 LOAD_METHOD 4 (extend)
154 LOAD_NAME 2 (key_list)
156 CALL_METHOD 1
158 POP_TOP
160 JUMP_ABSOLUTE 134
15 >> 162 LOAD_CONST 38 (<code object <listcomp> at 0x7f704e8a4df0, file "snake.py", line 15>)
164 LOAD_CONST 37 ('<listcomp>')
166 MAKE_FUNCTION 0
168 LOAD_NAME 5 (zip)
170 LOAD_NAME 0 (input_list)
172 LOAD_NAME 2 (key_list)
174 CALL_FUNCTION 2
176 GET_ITER
178 CALL_FUNCTION 1
180 STORE_NAME 6 (result)
18 182 LOAD_CONST 39 ('')
184 LOAD_METHOD 7 (join)
186 LOAD_NAME 8 (map)
188 LOAD_NAME 9 (chr)
190 LOAD_NAME 6 (result)
192 CALL_FUNCTION 2
194 CALL_METHOD 1
196 STORE_NAME 10 (result_text)
198 LOAD_CONST 40 (None)
200 RETURN_VALUE
Disassembly of <code object <listcomp> at 0x7f704e8a4d40, file "snake.py", line 9>:
9 0 BUILD_LIST 0
2 LOAD_FAST 0 (.0)
>> 4 FOR_ITER 12 (to 18)
6 STORE_FAST 1 (char)
8 LOAD_GLOBAL 0 (ord)
10 LOAD_FAST 1 (char)
12 CALL_FUNCTION 1
14 LIST_APPEND 2
16 JUMP_ABSOLUTE 4
>> 18 RETURN_VALUE
Disassembly of <code object <listcomp> at 0x7f704e8a4df0, file "snake.py", line 15>:
15 0 BUILD_LIST 0
2 LOAD_FAST 0 (.0)
>> 4 FOR_ITER 16 (to 22)
6 UNPACK_SEQUENCE 2
8 STORE_FAST 1 (a)
10 STORE_FAST 2 (b)
12 LOAD_FAST 1 (a)
14 LOAD_FAST 2 (b)
16 BINARY_XOR
18 LIST_APPEND 2
20 JUMP_ABSOLUTE 4
>> 22 RETURN_VALUE
看到第一個區塊,會發現 load_const 出現了很多次,第82行出現 store_name 。這裡的意思是將數字載入 register,並且最後將所有載入的數字儲存在名為 input_list 的陣列裡。例如第一行,便是將數字 4 存入 0 號 register。
1 0 LOAD_CONST 0 (4)
2 LOAD_CONST 1 (54)
4 LOAD_CONST 2 (41)
6 LOAD_CONST 3 (0)
8 LOAD_CONST 4 (112)
10 LOAD_CONST 5 (32)
12 LOAD_CONST 6 (25)
14 LOAD_CONST 7 (49)
16 LOAD_CONST 8 (33)
18 LOAD_CONST 9 (3)
20 LOAD_CONST 3 (0)
22 LOAD_CONST 3 (0)
24 LOAD_CONST 10 (57)
26 LOAD_CONST 5 (32)
28 LOAD_CONST 11 (108)
30 LOAD_CONST 12 (23)
32 LOAD_CONST 13 (48)
34 LOAD_CONST 0 (4)
36 LOAD_CONST 14 (9)
38 LOAD_CONST 15 (70)
40 LOAD_CONST 16 (7)
42 LOAD_CONST 17 (110)
44 LOAD_CONST 18 (36)
46 LOAD_CONST 19 (8)
48 LOAD_CONST 11 (108)
50 LOAD_CONST 16 (7)
52 LOAD_CONST 7 (49)
54 LOAD_CONST 20 (10)
56 LOAD_CONST 0 (4)
58 LOAD_CONST 21 (86)
60 LOAD_CONST 22 (43)
62 LOAD_CONST 23 (104)
64 LOAD_CONST 24 (44)
66 LOAD_CONST 25 (91)
68 LOAD_CONST 26 (7)
70 LOAD_CONST 27 (18)
72 LOAD_CONST 28 (106)
74 LOAD_CONST 29 (124)
76 LOAD_CONST 20 (89)
78 LOAD_CONST 30 (78)
80 BUILD_LIST 40
82 STORE_NAME 0 (input_list)
依序填入數字,便能得到 input_list 。
input_list = [4, 54, 41, 0, 112, 32, 25, 49, 33, 3, 0, 0, 57, 32, 108, 23, 48, 4, 9, 70, 7, 110, 36, 8, 108, 7, 49, 10, 4, 86, 43, 104, 44, 91, 7, 18, 106, 124, 89, 78]
接著看到 區塊 2 到 區塊 6。
看到 區塊 2 中先載入了 ‘J’,再將 J 儲存至 key_str 中。( 目前 key_str = ‘J’ )
看到 區塊 3,先載入了 '',再載入 key_str,也就是 J,然後將 ‘’ 和 key_str 相加,也就是 '_' + 'J',得到 '_J’ 後,再儲存至 key_str。( 目前 key_str = ‘_J‘ )
看到 區塊 4,先載入了 key_str ( ‘_J‘ ),並且將 key_str 和 o 相加,也就是 '_J’ + ‘o',得到 '_Jo',並且儲存進入 key_str。( 目前 key_str = ‘_Jo‘ )
看到 區塊 5,先載入了 key_str ( ‘_Jo‘ ),並且將 key_str 和 3 相加,也就是 '_Jo’ + ‘3',得到 '_Jo3',並且儲存進入 key_str。( 目前 key_str = ‘_Jo3‘ )
看到 區塊 6,先載入了 ’t’,再載入 key_str ( ‘_Jo3‘ ),並且將 ‘t’ 和 key_str 相加,也就是 ‘t’ + ‘_Jo3‘,得到 't_Jo3',並且儲存進入 key_str,得到 key_str = ‘t_Jo3‘ 。
2 84 LOAD_CONST 31 ('J')
86 STORE_NAME 1 (key_str)
3 88 LOAD_CONST 32 ('_')
90 LOAD_NAME 1 (key_str)
92 BINARY_ADD
94 STORE_NAME 1 (key_str)
4 96 LOAD_NAME 1 (key_str)
98 LOAD_CONST 33 ('o')
100 BINARY_ADD
102 STORE_NAME 1 (key_str)
5 104 LOAD_NAME 1 (key_str)
106 LOAD_CONST 34 ('3')
108 BINARY_ADD
110 STORE_NAME 1 (key_str)
6 112 LOAD_CONST 35 ('t')
114 LOAD_NAME 1 (key_str)
116 BINARY_ADD
118 STORE_NAME 1 (key_str)
key_str = 't_Jo3'
再來看到 區塊 9,會發現呼叫了 0x7ff3b9776d40 這個位置的函式,並且 key_str 會作為其的參數,並且這個 code 回傳的結果會被儲存在 key_list 中。
9 120 LOAD_CONST 36 (<code object <listcomp> at 0x7f704e8a4d40, file "snake.py", line 9>)
122 LOAD_CONST 37 ('<listcomp>')
124 MAKE_FUNCTION 0
126 LOAD_NAME 1 (key_str)
128 GET_ITER
130 CALL_FUNCTION 1
132 STORE_NAME 2 (key_list)
於是看到 0x7ff3b9776d40 這個位置的函式,從第4行可以看到這是一個有關 for 的 code。第 6 行能夠看到從 區塊 9 傳遞的參數 ( key_str ) 會被一個字元一個字元逐一讀取,並且會儲存 ord (char),所以能得當 key_list = [ ord(char) for char in key_str ]。
Disassembly of <code object <listcomp> at 0x7f704e8a4d40, file "snake.py", line 9>:
9 0 BUILD_LIST 0
2 LOAD_FAST 0 (.0)
>> 4 FOR_ITER 12 (to 18)
6 STORE_FAST 1 (char)
8 LOAD_GLOBAL 0 (ord)
10 LOAD_FAST 1 (char)
12 CALL_FUNCTION 1
14 LIST_APPEND 2
16 JUMP_ABSOLUTE 4
>> 18 RETURN_VALUE
key_list = [ord(char) for char in key_str]
緊接著看到 區塊 11、12,從 區塊 11 中第 148 行看到當 len(key_list) < len(input_list) == false 時才會 pop 至第 162 行,而當 len(key_list) < len(input_list) ==True,會載入 key_list,並且 extend key_list,並且再回到 134 行,所以我們能夠得到一個 while loop。
11 >> 134 LOAD_NAME 3 (len)
136 LOAD_NAME 2 (key_list)
138 CALL_FUNCTION 1
140 LOAD_NAME 3 (len)
142 LOAD_NAME 0 (input_list)
144 CALL_FUNCTION 1
146 COMPARE_OP 0 (<)
148 POP_JUMP_IF_FALSE 162
12 150 LOAD_NAME 2 (key_list)
152 LOAD_METHOD 4 (extend)
154 LOAD_NAME 2 (key_list)
156 CALL_METHOD 1
158 POP_TOP
160 JUMP_ABSOLUTE 134
while len(key_list) < len(input_list):
key_list.extend(key_list)
再來看到 區塊 15 ,首先會先呼叫 0x7ff3b9776df0 這個位置的函式,並且將 zip ( input_list, key_list ) 傳入,最後再將函式儲存的值命名為 result。
15 >> 162 LOAD_CONST 38 (<code object <listcomp> at 0x7f704e8a4df0, file "snake.py", line 15>)
164 LOAD_CONST 37 ('<listcomp>')
166 MAKE_FUNCTION 0
168 LOAD_NAME 5 (zip)
170 LOAD_NAME 0 (input_list)
172 LOAD_NAME 2 (key_list)
174 CALL_FUNCTION 2
176 GET_ITER
178 CALL_FUNCTION 1
180 STORE_NAME 6 (result)
於是我們看到 0x7ff3b9776df0 這個位置的函式,第 12 至 16 行可以看到,在每個 iteration 中,會載入 a b 這兩個變數,而這兩個變數是由 sector 15 傳入的 zip ( input_list, key_list ) 提供,再看到第 8、10、16 行,可以看到會將 a xor b 儲存進入 register。
Disassembly of <code object <listcomp> at 0x7f704e8a4df0, file "snake.py", line 15>:
15 0 BUILD_LIST 0
2 LOAD_FAST 0 (.0)
>> 4 FOR_ITER 16 (to 22)
6 UNPACK_SEQUENCE 2
8 STORE_FAST 1 (a)
10 STORE_FAST 2 (b)
12 LOAD_FAST 1 (a)
14 LOAD_FAST 2 (b)
16 BINARY_XOR
18 LIST_APPEND 2
20 JUMP_ABSOLUTE 4
>> 22 RETURN_VALUE
於是可以得到 result。
result = [a ^ b for a, b in zip(input_list, key_list)]
最後再看到 區塊 18,第 192 行 和 194 行可以知道,會先呼叫 map 這個 function,再呼叫 .join 這個 method,再把最後結果儲存在 result_text 裡。
18 182 LOAD_CONST 39 ('')
184 LOAD_METHOD 7 (join)
186 LOAD_NAME 8 (map)
188 LOAD_NAME 9 (chr)
190 LOAD_NAME 6 (result)
192 CALL_FUNCTION 2
194 CALL_METHOD 1
196 STORE_NAME 10 (result_text)
198 LOAD_CONST 40 (None)
200 RETURN_VALUE
結果如以下。
result_text = ''.join(map(chr, result))
最後完整個程式碼如下。
#!/usr/bin/env python3
input_list = [4, 54, 41, 0, 112, 32, 25, 49, 33, 3, 0, 0, 57, 32, 108, 23, 48, 4, 9, 70, 7, 110, 36, 8, 108, 7, 49, 10, 4, 86, 43, 104, 44, 91, 7, 18, 106, 124, 89, 78]
key_str = 't_Jo3'
key_list = [ord(char) for char in key_str]
while len(key_list) < len(input_list):
key_list.extend(key_list)
result = [a ^ b for a, b in zip(input_list, key_list)]
result_text = ''.join(map(chr, result))
print(result_text)
執行檔案,可以得到 flag。
$ python3 script.py
picoCTF{N0t_sO_coNfus1ng_sn@ke_7f44f566}
小結:
學習分析 bytecode。